package cn.hy.dataSync.alter.ding;

import cn.hy.dataSync.alter.Alert;
import cn.hy.dataSync.bean.HttpAsyncClient;
import com.alibaba.fastjson.JSON;
import com.google.common.base.Throwables;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.net.URLEncoder;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutorService;

import static java.util.concurrent.Executors.newSingleThreadExecutor;


/**
 *
 * ding ding 机器人接入
 * https://open.dingtalk.com/document/group/custom-robot-access
 * 预警接入
 */
@Slf4j
@Component
public final class DingTalkAlert implements Alert {

    private static final ExecutorService alterExecutor = newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("DingTalkAlert-%s").build());

    private static final int SUCCESS = 200;

    private static final String CHARSET = "UTF-8";

    private static final String LOG_HEAD = DingTalkAlert.class.getSimpleName();

    @Resource
    private HttpAsyncClient httpAsyncClient;

    public void send(@NotNull String url, @NotNull Type type) {
        Map<String, String> headers = Maps.newHashMap();
        headers.put("Content-Type", "application/json; CHARSET=" + CHARSET);

        alterExecutor.execute(() -> {
            httpAsyncClient.doRequest(
                    url,
                    headers,
                    JSON.toJSONString(type),
                    CHARSET,
                    new FutureCallback<HttpResponse>() {
                        @Override
                        public void completed(HttpResponse result) {
                            alterExecutor.execute(() -> {
                                if (SUCCESS == result.getStatusLine().getStatusCode()) {
                                    try {
                                        String responseString = EntityUtils.toString(result.getEntity(), CHARSET);
                                        log.info("{} send completed, response:[{}]", LOG_HEAD, responseString);
                                    } catch (Exception ex) {
                                        log.warn("{} send completed, but deal exception, cause: {}", LOG_HEAD, Throwables.getStackTraceAsString(ex));
                                    }
                                } else {
                                    log.warn("{} send failed, statusCode:[{}]", LOG_HEAD, result.getStatusLine().getStatusCode());
                                }
                            });
                        }

                        @Override
                        public void failed(Exception ex) {
                            alterExecutor.execute(() -> {
                                log.error("{} send fail, cause: {}", this.getClass().getSimpleName(), Throwables.getStackTraceAsString(ex));
                            });
                        }

                        @Override
                        public void cancelled() {

                        }
                    }
            );
        });
    }

    public void send(@NotNull String url,  String secret, @NotNull Type type) {
        send(Optional.ofNullable(secret).map(sct -> url.concat(sign(sct).getParams())).orElse(url), type);
    }

    private static Sign sign(String secret) {
        try {
            Long timestamp = System.currentTimeMillis();
            String stringToSign = timestamp + "\n" + secret;
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(secret.getBytes(CHARSET), "HmacSHA256"));
            byte[] signData = mac.doFinal(stringToSign.getBytes(CHARSET));

            return Sign.builder()
                    .timestamp(timestamp)
                    .sign(URLEncoder.encode(Base64.getEncoder().encodeToString(signData), CHARSET))
                    .build();
        } catch (Throwable throwable) {
            throw new RuntimeException("sign exception", throwable);
        }
    }

    @Data
    @Builder
    public static class Text implements Type {

        private static final long serialVersionUID = 2196176304277892587L;

        private final String msgtype = "text";

        private Detail text;

        private At at;

        @Data
        @Builder
        public static class Detail implements Serializable {

            private static final long serialVersionUID = 7314335633009818666L;

            private String content;
        }
    }

    @Data
    @Builder
    public static class Link implements Type {

        private static final long serialVersionUID = 5773482911609282674L;

        private final String msgtype = "link";

        private Detail link;

        @Data
        @Builder
        public static class Detail implements Serializable {

            private static final long serialVersionUID = 7314335633009818666L;

            private String messageUrl;

            private String picUrl;

            private String title;

            private String text;
        }

        private At at;
    }

    @Data
    @Builder
    public static class Markdown implements Type {

        private static final long serialVersionUID = -8890525849118586668L;

        private final String msgtype = "markdown";

        private Detail markdown;

        @Data
        @Builder
        public static class Detail implements Serializable {

            private static final long serialVersionUID = 6006593995576196005L;

            private String title;

            private String text;
        }

        private At at;
    }

    @Data
    @Builder
    public static class At implements Serializable {

        private static final long serialVersionUID = -2467282664112664712L;

        private List<String> atMobiles;

        private Boolean isAtAll = Boolean.FALSE;
    }

    public interface Type extends Serializable {
    }

    @Data
    static class Response implements Serializable {

        private static final long serialVersionUID = 1974519834763009666L;

        private int errcode;

        private String errmsg;
    }

    @Data
    @Builder
    private static class Sign implements Serializable {

        private static final long serialVersionUID = -2163847224301906663L;

        private Long timestamp;

        private String sign;

        public String getParams() {
            return "&timestamp=" +
                    timestamp +
                    "&sign=" +
                    sign;
        }
    }
}
